#include "Sphere.h"
#include <math.h>
#include <string>
#include <string.h>  /* for memset() */

/* M_PI is not defined on windows for some reason. */
#ifndef M_PI
#define M_PI 3.14159265358979323846264338327
#endif

Sphere::Sphere(std::ifstream& inputFile){
	position[3] = 1.f;
	std::string textureFile;
	inputFile >> name >> position[0] >> position[1] >> position[2];
	inputFile >> scaleX >> scaleY >> scaleZ >> color.r >> color.g >> color.b;
	inputFile >> ambienceConstant >> diffusionConstant >> specularConstant;
	inputFile >> reflectionConstant >> specularExponent;
    inputFile >> transparency >> refractionIndex;
	inputFile >> textureFile;
	
	if(textureFile.compare("none") != 0)
		texture.readImage(textureFile);
		
	transformArray[0] = 1.0/scaleX;
	transformArray[1] = 1.0/scaleY;
	transformArray[2] = 1.0/scaleZ;
	transformArray[3] = -position[0]/scaleX;
	transformArray[4] = -position[1]/scaleY;
	transformArray[5] = -position[2]/scaleZ;
}

Vector Sphere::transform(Vector& v1){
	return Vector(transformArray[0] * v1[0] + transformArray[3] * v1[3],
				  transformArray[1] * v1[1] + transformArray[4] * v1[3],
				  transformArray[2] * v1[2] + transformArray[5] * v1[3], v1[3]);
}

Vector Sphere::normal(Vector &surfacePoint){
	//Compute the normal of the ellipsoid using the gradient formula
	Vector originSurfacePoint = surfacePoint - position;
	Vector normal = Vector(2.0 * originSurfacePoint[0] / pow(scaleX, 2), 
						   2.0 * originSurfacePoint[1] / pow(scaleY, 2),
						   2.0 * originSurfacePoint[2] / pow(scaleZ, 2), 0.0);
	normal.normalize();
	return normal;
}

std::vector<double> Sphere::intersection(Ray& ray){
	// Apply the inverse transformation matrix to the ray
	Vector transformedDirection = transform(ray.direction);
	Vector transformedPoint = transform(ray.initialPoint);
	std::vector<double> results;

	// Start solving the intersection equations
	double a = pow(transformedDirection.magnitude(), 2);
	double b = transformedPoint.dotProduct(transformedDirection);
	double c = pow(transformedPoint.magnitude(), 2) - 1.0;
	double discriminant = b * b - a * c;
	if(discriminant > -doubleResolution){
		if(discriminant < doubleResolution){// One solution
			double t = -b / a;
			if(t >= doubleResolution){
				results.push_back(t);
			}
		}
		else{// two solutions
			double t1 = (-b + sqrt(discriminant))/a;
			double t2 = (-b - sqrt(discriminant))/a;
			double nearT, farT;
			if(t2 < t1){
				nearT = t2;
				farT = t1;
			}
			else{
				nearT = t1;
				farT = t2;
			}
			if(nearT < doubleResolution){
				if(farT >= doubleResolution){// Camera is inside sphere
					results.push_back(farT);
				}
				// else Sphere is behind camera
			}
			else{// Sphere is in front of camera
				results.push_back(nearT);
				results.push_back(farT);
			}
		}
	}
	// If there are no positive solutions, return an empty vector
	return results;
}

bool Sphere::contains(Vector& point){
	Vector difference = point - position;
	return transform(difference).magnitude() < 1.0 + doubleResolution;
}

Color Sphere::getColor(Vector& pos){
	Color value = color;
	
	if(texture.hasTexture()) {
		// find the smallest scale to use for radius
		double radius = scaleX;
		double rradius = 1.0 / radius;
		if(radius > scaleY)
			radius = scaleY;
		if(radius > scaleZ)
			radius = scaleZ;
		
		// set vectors for texture mapping
		Vector vn = Vector(0, 1, 0, 0);
		Vector ve = Vector(1, 0, 0, 0);
		Vector vc = vn.cross(ve);
		
		// find the current position in terms of a perfect sphere
		Vector newPosition = Vector(pos[0]*radius/scaleX, pos[1]*radius/scaleY, pos[2]*radius/scaleZ, pos[3]);
		
		// calculate using new position
		Vector vp = (newPosition - position) * rradius;
		double phi = acos(-vp.dotProduct(vn));
		double u; 
		double v = phi / M_PI;
		double theta = (acos(ve.dotProduct(vp) / sin(phi))) * (2.0 / M_PI);
		if (vc.dotProduct(vp) >= 0) 
			u = (1.0 - theta);
		else 
			u = theta;
		value = texture.GetPixel(u, v)*color;
	}
		
	return value;
}
